%%%-------------------------------------------------------------------
%%% @author Rain
%%% @copyright (C) 2022, <COMPANY>
%%% @doc
%%% @end
%%%-------------------------------------------------------------------
-module(tower_aoi_svr).

-behaviour(gen_server).
-include("tower_aoi.hrl").
-include("obj.hrl").

-export([start_map/2, start_link/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
    code_change/3]).

-define(SERVER, ?MODULE).

-record(state, {
    tower_x = 0,
    tower_y = 0,
    tower_maps = #{}, %% #{AoiPos:{AoiX, AoiY} => #tower_data{}}
    object_data_maps = #{}  %% #{Uuid => #object{}}

}).

%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

start_map(X, Y) ->
    InitMapData = #{"map_x" => X, "map_y" => Y},
    ChildSpec = #{id => ?MODULE,
        start => {?MODULE, start_link, [InitMapData]},
        restart => permanent,
        shutdown => 2000,
        type => worker,
        modules => [?MODULE]},
    supervisor:start_child(tower_aoi_app_sup, ChildSpec).

start_link(InitMapData) ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [InitMapData], []).

init([InitMapData = #{"map_x" := MapX, "map_y" := MapY}]) ->
    TowerX = maps:get("tower_x", InitMapData, ?DEFAULT_TOWER_X),
    TowerY = maps:get("tower_y", InitMapData, ?DEFAULT_TOWER_Y),
    tower_aoi:init_tower_aoi_grid_data(MapX, MapY, TowerX, TowerY),
    {ok, #state{tower_x = TowerX, tower_y = TowerY}};
init(_) ->
    {stop, arg_invalid}.

handle_call({enter_map, Uid, X, Y}, _From, State = #state{}) ->
    State1 = enter_map(Uid, X, Y, State),
    {reply, ok, State1};

handle_call({leave_map, Uid}, _From, State = #state{}) ->
    State1 = leave_map(Uid, State),
    {reply, ok, State1};

handle_call(_Request, _From, State = #state{}) ->
    {reply, ok, State}.

handle_cast({sync_map, Uid, X, Y}, State = #state{}) ->
    State1 = sync_map(Uid, X, Y, State),
    {noreply, State1};


handle_cast(_Info, State = #state{}) ->
    {noreply, State}.

handle_info(_Info, State = #state{}) ->
    {noreply, State}.

terminate(_Reason, _State = #state{}) ->
    ok.

code_change(_OldVsn, State = #state{}, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================


%%%===================================================================
%%% Hook functions
%%%===================================================================

%% @doc 进地图前钩子
enter_map_before_hook(_Uid, _X, _Y, State) ->
    State.

%% @doc 进地图后钩子
enter_map_after_hook(_Uid, _X, _Y, State) ->
    State.

%% @doc 离开地图前钩子
leave_map_before_hook(_Uid, State) ->
    State.

%% @doc 离开地图后钩子
leave_map_after_hook(_Uid, State) ->
    State.

%% @doc 地图内移动前钩子
sync_map_before_hook(_Uid, _X, _Y, State) ->
    State.

%% @doc 地图内移动后钩子
sync_map_after_hook(_Uid, _X, _Y, State) ->
    State.


%%%===================================================================
%%% Private functions
%%%===================================================================

%% @doc 进入场景
%% @return: #state{}
-spec enter_map(Uid, X, Y, State) -> State1 when
    Uid :: #uid{},
    X :: integer(),
    Y :: integer(),
    State :: #state{},
    State1 :: #state{}.
enter_map(#uid{value = UuidValue} = Uid, X, Y, State = #state{object_data_maps = ObjectDataMaps}) ->
    case ObjectDataMaps of
        #{UuidValue := #object{}} ->
            State1 = leave_map(Uid, State);
        _ ->
            State1 = State
    end,
    do_enter_map(Uid, X, Y, State1).

do_enter_map(Uid, X, Y, State) ->
    State1 = enter_map_before_hook(Uid, X, Y, State),
    State2 = add_object(Uid, X, Y, State1),
    enter_map_after_hook(Uid, X, Y, State2).

%% @doc 离开场景
-spec leave_map(Uid, State) -> State1 when
    Uid :: #uid{},
    State :: #state{},
    State1 :: #state{}.
leave_map(Uid, State) ->
    do_leave_map(Uid, State).

do_leave_map(Uid, State) ->
    State1 = leave_map_before_hook(Uid, State),
    State2 = del_object(Uid, State1),
    leave_map_after_hook(Uid, State2).

%% @doc 同步场景位置
-spec sync_map(Uid, X, Y, State) -> State1 when
    Uid :: #uid{},
    X :: integer(),
    Y :: integer(),
    State :: #state{},
    State1 :: #state{}.
sync_map(Uid, X, Y, State) ->
    do_sync_map(Uid, X, Y, State).

do_sync_map(Uid, X, Y, State) ->
    State1 = sync_map_before_hook(Uid, X, Y, State),
    State2 = move_object(Uid, X, Y, State1),
    sync_map_after_hook(Uid, X, Y, State2).

%% @doc 添加一个Object
add_object(#uid{value = UidValue} = Uid, X, Y, #state{tower_maps = TowerMaps, object_data_maps = ObjectDataMaps} = State) ->
    AoiPos = tower_aoi:piex_pos_2_aoi_pos(State#state.tower_x, State#state.tower_y, X, Y),
    case tower_aoi:get_tower_data(TowerMaps, AoiPos) of
        #tower_data{} = TowerData ->
            ViewportAoi = tower_aoi:get_viewport_aoi(AoiPos),
            {Object, TowerMaps1} = add_object1(Uid, X, Y, TowerData, TowerMaps),

            ObjectDataMaps1 = maps:put(UidValue, Object, ObjectDataMaps),
            State1 = State#state{tower_maps = TowerMaps1, object_data_maps = ObjectDataMaps1},
            add_object_notice_watchers(AoiPos, TowerMaps, Object),
            add_object_notice_viewport(ViewportAoi, TowerMaps, Object),
            State1;
        _ ->
            State
    end.

%% @doc 添加单个Object
add_object1(#uid{value = UidValue} = Uid, X, Y, TowerData, TowerMaps) ->
    Object = #object{uid_value = UidValue, uid = Uid, x = X, y = Y},
    TowerMaps1 = add_object1(Uid, Object, TowerData, TowerMaps),
    {Object, TowerMaps1}.
add_object1(Uid = #uid{value = UidValue}, Object,
    #tower_data{aoi_pos = AoiPos, object_maps = ObjectMaps} = TowerData, TowerMaps) ->

    ObjAoiType = tower_aoi:get_object_aoi_type_by_uid(Uid),
    ObjectMaps1 = ObjectMaps#{UidValue => Object},
    TowerData1 = TowerData#tower_data{object_maps = ObjectMaps1},
    TowerMaps1 = tower_aoi:set_tower_data(TowerMaps, AoiPos, TowerData1),
    case ObjAoiType of
        ?OBJ_AOI_TYPE_WATCHER ->
            TowerMaps2 = add_object_as_watcher(Object, AoiPos, TowerMaps1);
        _ ->
            TowerMaps2 = TowerMaps1
    end,
    TowerMaps2.

%% @doc 添加Object通知Watcher
add_object_notice_watchers(AoiPos, TowerMaps, Object) ->
    Msg = {appear, Object},
    send_book_watchers_msg(Msg, [AoiPos], TowerMaps, [Object#object.uid_value]).

%% @doc 添加Object若为watcher通知自身视野
add_object_notice_viewport(ViewportAoi, TowerMaps, #object{uid = Uid} = Object) ->
    case tower_aoi:get_object_aoi_type_by_uid(Uid) of
        ?OBJ_AOI_TYPE_WATCHER ->
            AppearObjects = get_viewport_objects(ViewportAoi, TowerMaps),
            ViewportMsg = {viewport, [{appear, AppearObjects}]},
            send_watchers_msg(Object, ViewportMsg);
        _ ->
            pass
    end.

%% @doc 删除一个Object
del_object(#uid{value = UidValue} = Uid, #state{tower_maps = TowerMaps, object_data_maps = ObjectDataMaps} = State) ->
    case ObjectDataMaps of
        #{UidValue := #object{x = X, y = Y} = Object} ->
            AoiPos = tower_aoi:piex_pos_2_aoi_pos(State#state.tower_x, State#state.tower_y, X, Y),
            case tower_aoi:get_tower_data(TowerMaps, AoiPos) of
                #tower_data{} = TowerData ->
                    ViewportAoi = tower_aoi:get_viewport_aoi(AoiPos),
                    TowerMaps1 = del_object1(Uid, TowerData, TowerMaps),
                    ObjectDataMaps1 = maps:remove(UidValue, ObjectDataMaps),
                    State1 = State#state{tower_maps = TowerMaps1, object_data_maps = ObjectDataMaps1},
                    del_object_notice_watchers(AoiPos, TowerMaps, Object),
                    del_object_notice_viewport(ViewportAoi, TowerMaps, Object),
                    State1;
                _ ->
                    State
            end;
        _ ->
            State
    end.

%% @doc 删除单个Object
del_object1(#uid{value = UidValue} = Uid,
    #tower_data{aoi_pos = AoiPos, object_maps = ObjectMaps} = TowerData, TowerMaps) ->

    ObjAoiType = tower_aoi:get_object_aoi_type_by_uid(Uid),
    ObjectMaps1 = maps:remove(UidValue, ObjectMaps),
    TowerData1 = TowerData#tower_data{object_maps = ObjectMaps1},
    TowerMaps1 = tower_aoi:set_tower_data(TowerMaps, AoiPos, TowerData1),
    case ObjAoiType of
        ?OBJ_AOI_TYPE_WATCHER ->
            TowerMaps2 = del_object_as_watcher(UidValue, AoiPos, TowerMaps1);
        _ ->
            TowerMaps2 = TowerMaps
    end,
    TowerMaps2.

%% @doc 删除Object通知Watcher
del_object_notice_watchers(AoiPos, TowerMaps, Object) ->
    Msg = {disappear, Object},
    send_book_watchers_msg(Msg, [AoiPos], TowerMaps, [Object#object.uid_value]).

%% @doc 删除Object若为watcher通知自身视野
%% @note 本方法可要可不要,因为已经离开地图,后续数据不重要
del_object_notice_viewport(ViewportAoi, TowerMaps, #object{uid = Uid, uid_value = UidValue} = Object) ->
    case tower_aoi:get_object_aoi_type_by_uid(Uid) of
        ?OBJ_AOI_TYPE_WATCHER ->
            DisappearObjects = get_viewport_objects(ViewportAoi, TowerMaps),
            DisappearObjects1 = [Acc || Acc = #object{uid_value = AccUidValue}
                <- DisappearObjects, AccUidValue =/= UidValue],
            ViewportMsg = {viewport, [{disappear, DisappearObjects1}]},
            send_watchers_msg(Object, ViewportMsg);
        _ ->
            pass
    end.

%% @doc 移动单个Object
move_object(#uid{value = UidValue} = Uid, X, Y, #state{tower_maps = TowerMaps, object_data_maps = ObjectDataMaps} = State) ->
    TowerX = State#state.tower_x,
    TowerY = State#state.tower_y,
    case ObjectDataMaps of
        #{UidValue := #object{x = X, y = Y}} -> % 没有移动过
            State;
        #{UidValue := #object{x = OldX, y = OldY} = Object} ->
            OldAoiPos = tower_aoi:piex_pos_2_aoi_pos(TowerX, TowerY, OldX, OldY),
            AoiPos = tower_aoi:piex_pos_2_aoi_pos(TowerX, TowerY, X, Y),
            OldTowerData = tower_aoi:get_tower_data(TowerMaps, OldAoiPos),
            TowerData = tower_aoi:get_tower_data(TowerMaps, AoiPos),
            Object1 = Object#object{uid = Uid, uid_value = UidValue, x = X, y = Y},
            {TowerMaps1, MoveObjects, AppearObjects, DisappearObjects} =
                move_object1(Uid, Object1, OldTowerData, TowerData, TowerMaps),
            ObjectDataMaps1 = maps:put(UidValue, Object1, ObjectDataMaps),
            State1 = State#state{tower_maps = TowerMaps1, object_data_maps = ObjectDataMaps1},
            move_object_notice_watchers(MoveObjects, AppearObjects, DisappearObjects, Object1),
            move_object_notice_viewport(AppearObjects, DisappearObjects, Object1),
            State1;
        _ ->
            logger:error("MoveObject: Object ~p doesn't in map", [Uid]),
            State
    end.

move_object1(#uid{} = Uid, Object,
    #tower_data{aoi_pos = OldAoiPos} = OldTowerData,
    #tower_data{aoi_pos = AoiPos} = TowerData, TowerMaps) ->
    OldViewportAoi = tower_aoi:get_viewport_aoi(OldAoiPos),
    ViewportAoi = tower_aoi:get_viewport_aoi(AoiPos),
    {MoveObjects, AppearObjects, DisappearObjects} = filter_move_object_viewport(ViewportAoi, OldViewportAoi, TowerMaps),
    TowerMaps1 = move_object2(Uid, TowerData, OldTowerData, Object, TowerMaps),
    {TowerMaps1, MoveObjects, AppearObjects, DisappearObjects}.

move_object2(_Uid,
    #tower_data{aoi_pos = AoiPos},
    #tower_data{aoi_pos = AoiPos},
    _Object, TowerMaps) -> % 在同一个Aoi格子
    TowerMaps;
move_object2(#uid{} = Uid, TowerData, OldTowerData, Object, TowerMaps) ->
    TowerMaps1 = del_object1(Uid, OldTowerData, TowerMaps),
    TowerMaps2 = add_object1(Uid, Object, TowerData, TowerMaps1),
    TowerMaps2.

%% @doc 移动Object通知Watcher
move_object_notice_watchers(MoveObjects, AppearObjects, DisappearObjects,
    #object{uid_value = UidValue} = Object) ->
    MoveMsg = {move, Object},
    DisappearMsg = {disappear, Object},
    AppearMsg = {appear, Object},

    send_watchers_msg(filter_watchers(MoveObjects), MoveMsg, [UidValue]),
    send_watchers_msg(filter_watchers(DisappearObjects), DisappearMsg, [UidValue]),
    send_watchers_msg(filter_watchers(AppearObjects), AppearMsg, [UidValue]).

%% @doc 移动Object若为Watcher通知自身视野改变
move_object_notice_viewport(AppearObjects, DisappearObjects, #object{uid = Uid} = Object) ->
    case tower_aoi:get_object_aoi_type_by_uid(Uid) of
        ?OBJ_AOI_TYPE_WATCHER ->
            ViewportMsg = {viewport, [{disappear, DisappearObjects}, {appear, AppearObjects}]},
            send_watchers_msg(Object, ViewportMsg);
        _ ->
            pass
    end.


%% @doc 给订阅当前ViewportAoi的Watchers发送视野信息
send_book_watchers_msg(Msg, ViewportAoi, TowerMaps, FilterUidList) ->
    Watchers  = get_viewport_watchers(ViewportAoi, TowerMaps),
    send_watchers_msg(Watchers, Msg, FilterUidList).

%% @doc 发送消息给观察者
send_watchers_msg(Watchers, Msg) ->
    send_watchers_msg(Watchers, Msg, []).
send_watchers_msg(Watcher = #object{}, Msg, FilterUidList) ->
    send_watchers_msg([Watcher], Msg, FilterUidList);
send_watchers_msg(Watchers = [_ | _], Msg, FilterUidList) ->
    UidList = [Uid || #object{uid = Uid, uid_value = UidValue} <- Watchers,
        not lists:member(UidValue, FilterUidList)],
    tower_aoi:broadcast(UidList, Msg);
send_watchers_msg(_, _, _) ->
    pass.

%% @doc 过滤watchers
filter_watchers(Objects) ->
    [Acc||Acc = #object{uid = Uid}<-Objects,
        tower_aoi:get_object_aoi_type_by_uid(Uid) == ?OBJ_AOI_TYPE_WATCHER].

%% @doc 根据视野不同过滤不同视野数据
filter_move_object_viewport(ViewportAoi, ViewportAoi, TowerMaps) -> % 同一个Aoi格子只有move
    MoveObjects = get_viewport_objects(ViewportAoi, TowerMaps),
    {MoveObjects, [], []};
filter_move_object_viewport(ViewportAoi, OldViewportAoi, TowerMaps) ->
    Fun1 =
        fun(AccAoiPos, {AccDisappearObjects, AccMoveObjects}) ->
            case lists:member(AccAoiPos, ViewportAoi) of
                true ->
                    AccObjects = get_viewport_objects(AccAoiPos, TowerMaps),
                    {AccDisappearObjects, lists:usort(AccObjects ++ AccMoveObjects)};
                _ ->
                    AccObjects = get_viewport_objects(AccAoiPos, TowerMaps),
                    {lists:usort(AccObjects ++ AccDisappearObjects), AccMoveObjects}
            end
        end,

    {DisappearObjects, MoveObjects1} = lists:foldl(Fun1, {[], []}, OldViewportAoi),

    Fun2 =
        fun(AccAoiPos, {AccAppearObjects, AccMoveObjects}) ->
            case lists:member(AccAoiPos, OldViewportAoi) of
                true ->
                    AccObjects = get_viewport_objects(AccAoiPos, TowerMaps),
                    {AccAppearObjects, lists:usort(AccObjects ++ AccMoveObjects)};
                _ ->
                    AccObjects = get_viewport_objects(AccAoiPos, TowerMaps),
                    {lists:usort(AccObjects ++ AccAppearObjects), AccMoveObjects}
            end
        end,

    {AppearObjects, MoveObjects} = lists:foldl(Fun2, {[], MoveObjects1}, ViewportAoi),

    {MoveObjects, AppearObjects, DisappearObjects}.


%% @doc 获取视野范围内的所有Watcher
get_viewport_watchers(ViewportAoi, TowerMaps) ->
    get_viewport_watchers(ViewportAoi, TowerMaps, []).
get_viewport_watchers(AoiPos = {_, _}, TowerMaps, Watchers) ->
    get_viewport_watchers([AoiPos], TowerMaps, Watchers);
get_viewport_watchers([] = _ViewportAoi, _TowerMaps, Watchers) ->
    Watchers;
get_viewport_watchers([AoiPos | T] = _ViewportAoi, TowerMaps, Watchers) ->
    case tower_aoi:get_tower_data(TowerMaps, AoiPos) of
        #tower_data{watchers = AddWatchers = [_ | _]} ->
            get_viewport_watchers(T, TowerMaps, lists:usort(AddWatchers ++ Watchers));
        _ ->
            get_viewport_watchers(T, TowerMaps, Watchers)
    end.

%% @doc 获取视野范围内的所有Object
get_viewport_objects(ViewportAoi, TowerMaps) ->
    get_viewport_objects(ViewportAoi, TowerMaps, []).
get_viewport_objects(AoiPos = {_, _}, TowerMaps, Objects) ->
    get_viewport_objects([AoiPos], TowerMaps, Objects);
get_viewport_objects([] = _ViewportAoi, _TowerMaps, Objects) ->
    Objects;
get_viewport_objects([AoiPos | T] = _ViewportAoi, TowerMaps, Objects) ->
    case tower_aoi:get_tower_data(TowerMaps, AoiPos) of
        #tower_data{object_maps = ObjectMaps} when map_size(ObjectMaps) > 0 ->
            AddObjects = maps:values(ObjectMaps),
            get_viewport_objects(T, TowerMaps, lists:usort(AddObjects ++ Objects));
        _ ->
            get_viewport_objects(T, TowerMaps, Objects)
    end.

%% @doc 将object作为watcher添加
add_object_as_watcher(Object, AoiPos, TowerMaps) ->
    % 视野范围内必定包含当前AoiPos
    ViewportAoi = tower_aoi:get_viewport_aoi(AoiPos),
    {_, TowerMaps1} = lists:foldl(fun add_object_as_watcher1/2, {Object, TowerMaps}, ViewportAoi),
    TowerMaps1.


add_object_as_watcher1(AoiPos, {#object{uid_value = UidValue} = Object, TowerMaps}) ->
    case tower_aoi:get_tower_data(TowerMaps, AoiPos) of
        #tower_data{watchers = Watchers} = TowerData ->
            Watchers1 = lists:keystore(UidValue, #object.uid_value, Watchers, Object),
            TowerData1 = TowerData#tower_data{watchers = Watchers1},
            TowerMaps1 = tower_aoi:set_tower_data(TowerMaps, AoiPos, TowerData1),
            {Object, TowerMaps1};
        _ ->
            {Object, TowerMaps}
    end.


%% @doc 将object作为watcher添加删除
del_object_as_watcher(UidValue, AoiPos, TowerMaps) ->
    % 视野范围内必定包含当前AoiPos
    ViewportAoi = tower_aoi:get_viewport_aoi(AoiPos),
    {_, TowerMaps1} = lists:foldl(fun del_object_as_watcher1/2, {UidValue, TowerMaps}, ViewportAoi),
    TowerMaps1.

del_object_as_watcher1(AoiPos, {UidValue, TowerMaps}) ->
    case tower_aoi:get_tower_data(TowerMaps, AoiPos) of
        #tower_data{watchers = Watchers} = TowerData ->
            Watchers1 = lists:keydelete(UidValue, #object.uid_value, Watchers),
            TowerData1 = TowerData#tower_data{watchers = Watchers1},
            TowerMaps1 = tower_aoi:set_tower_data(TowerMaps, AoiPos, TowerData1),
            {UidValue, TowerMaps1};
        _ ->
            {UidValue, TowerMaps}
    end.